package tech.yixiyun.framework.kuafu.db.transaction;


import tech.yixiyun.framework.kuafu.bean.BeanMode;
import tech.yixiyun.framework.kuafu.bean.annotation.Bean;
import tech.yixiyun.framework.kuafu.config.AppConfig;
import tech.yixiyun.framework.kuafu.config.AppConfigException;
import tech.yixiyun.framework.kuafu.config.ConfigKey;
import tech.yixiyun.framework.kuafu.db.session.DbSession;
import tech.yixiyun.framework.kuafu.db.session.DbSessionContext;
import tech.yixiyun.framework.kuafu.enhance.hancer.IEnhancer;
import tech.yixiyun.framework.kuafu.log.LOGGER;
import tech.yixiyun.framework.kuafu.service.Result;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 事务管理，需要配合 @Transaction 注解使用
 */
@Bean(mode = BeanMode.MULTITON, remark = "事务管理")
public class TransactionEnhancer implements IEnhancer {

    /**
     * 记录方法是否需要开启事务，如果需要，记录事务等级，不需要就记录-1
     */
    private static final ConcurrentHashMap<Method, TransactionLevel> NEED_ENHANCE = new ConcurrentHashMap<>(128);

    //会话是不是从这开始的
    private boolean sessionFromHere = false;
    //当前事务是不是从这开启的
    private boolean transactionFromHere = false;

    //事务耗时
    private long costMillis = 0;



    @Override
    public void beforeDo(Object instance, Method method, Object[] args) {

        if (DbSessionContext.getSession() == null) {
            sessionFromHere = true;
            DbSession session = new DbSession();
            DbSessionContext.insertSession(session);
//            LOGGER.trace("创建session：{}, {}",session.hashCode(), method);
        }
        //先看之前是否解析过这个方法
        TransactionLevel level = NEED_ENHANCE.get(method);
        if (level == TransactionLevel.NONE) {
            return;
        }
        //没解析过，就解析一下
        if (level == null) {
            level = ifNeedOpenTransaction(method);
            NEED_ENHANCE.put(method, level);
            if (level == TransactionLevel.NONE) {
                return;
            }
        }

        //不是自动提交的，就需要手动开启事务，就需要一个session了
        DbSession dbSession = DbSessionContext.getSession();
        if (dbSession.getTransactionLevel() == TransactionLevel.NONE) {
            transactionFromHere = true;
            costMillis = System.currentTimeMillis();
//            LOGGER.trace("开启事务：{}", method);
            dbSession.setLevel(level);
            return;
        }

    }


    /**
     * 判断这个方法是否需要开启事务，如果需要，返回事务等级，不需要返回-1
     * @param method
     * @return
     */
    private TransactionLevel ifNeedOpenTransaction(Method method) {
        TransactionLevel level = TransactionLevel.NONE;
        Transaction transaction = method.getDeclaredAnnotation(Transaction.class);
        if (transaction != null) {
            level = transaction.level();
        } else {
            String[] prefixs = AppConfig.getAsObject(ConfigKey.TRANSACTION_PREFIX, String[].class);
            if (prefixs != null && prefixs.length > 0) {
                for (String prefix : prefixs) {
                    if (method.getName().startsWith(prefix)) {
                        level = AppConfig.getAsEnum(ConfigKey.DB_TRANSACTION_DEFAULTLEVEL, TransactionLevel.class);

                        if (level == null) {
                            throw new AppConfigException("配置项【"+ConfigKey.DB_TRANSACTION_DEFAULTLEVEL +"】 的值无效");
                        }

                        break;
                    }
                }
            }
        }
        return level;
    }


    @Override
    public void afterDo(Object instance, Method method, Object[] args, Object returnValue) {

        if (transactionFromHere == false) return;

        //返回结果是ServiceResult并且标记了是ERROR，就要回滚事务
        if (returnValue != null && returnValue instanceof Result && ((Result) returnValue).isError()) {

            DbSessionContext.rollback();
        } else {
            DbSessionContext.commit();
        }

    }


    @Override
    public void afterExceptionDo(Object instance, Method method, Object[] args, Throwable e) {
        if (transactionFromHere) {
            //发生异常了，要回滚事务
            DbSessionContext.rollback();
        }

        if (e instanceof RuntimeException) {
            throw (RuntimeException)e;
        }
        throw new RuntimeException(e);
    }

    @Override
    public void finishDo(Object instance, Method method, Object[] args) {

        if (transactionFromHere ) {
//            LOGGER.trace("即将结束事务，重置session：{}", method);
            DbSessionContext.resetSession();

            Long timeout = AppConfig.getAsLong(ConfigKey.DB_TRANSACTION_TIMEOUT_WARNING, 1000);
            costMillis = System.currentTimeMillis() - costMillis;
            if (costMillis > timeout) {
                LOGGER.warn("方法【{}】中事务从开启到关闭耗时{}ms", method, costMillis);
            }


        }
        if (sessionFromHere) {
//            LOGGER.trace("即将关闭session：{}", method);
            DbSessionContext.removeSession();
        }

    }
}
